﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using SilverlightContrib.IO.Compression.Zip;
using XamlaApp.Helpers;
using XamlaApp.Interfaces;
using XamlaApp.ViewModels;
using XamlaApp.Views.ElementViews;

namespace XamlaApp.Model.Compression
{
    /// <summary>
    /// Provides serialization and compression services for Xamla Projects
    /// </summary>
    public sealed class XamlaCompression : IDisposable
    {
        /// <summary>
        /// Serializes and compresses a list of XamlaElements to an array suitable for file streaming
        /// </summary>
        /// <param name="elements">List of Xamla elements to process.</param>
        /// <returns></returns>
        internal byte[] CompressProject(IEnumerable<IXamlaElement> elements)
        {
            var mvm = Container.AppContainer.Resolve<MainUIViewModel>();
            var uni = new UnicodeEncoding();

            //build an list of byte[] representing the project
            var elementStreams = new List<byte[]>();
            foreach (var e in elements)
            {
                e.ViewModel.PreSerializeActions();
                var text = JSONHelper.ConvertObjectToJsonString(e.ViewModel);
                var getBytes = uni.GetBytes(text.ToCharArray());
                elementStreams.Add(getBytes);
            }

            //now build the zip file containing each item as it's own "file"
            using (MemoryStream zippedMemoryStream = new MemoryStream())
            {

                using (ZipOutputStream zipOutputStream = new ZipOutputStream(zippedMemoryStream))
                {
                    zipOutputStream.SetLevel(1);
                    var i = 0;
                    foreach (var e in elements)
                    {
                        e.ViewModel.PreSerializeActions();
                        var text = JSONHelper.ConvertObjectToJsonString(e.ViewModel);

                        var getBytes = uni.GetBytes(text.ToCharArray());

                        ZipEntry entry = new ZipEntry(e.GetType().Name + "_(" + e.ViewModel.ControlName + ")" + i++.ToString() + ".xel");
                        zipOutputStream.PutNextEntry(entry);
                        zipOutputStream.Write(getBytes, 0, getBytes.Length);
                        //if we have an image element, then save the image data as well...
                        if (e is ImageView)
                        {
                            var imageEntry = new ZipEntry(e.GetType().Name + "_(" + e.ViewModel.ControlName + ")" + i++.ToString() + ".img");
                            var imageBytes = ConvertToByteArray(ImageViewModel.ImageData);
                            zipOutputStream.PutNextEntry(imageEntry);
                            zipOutputStream.Write(imageBytes, 0, imageBytes.Length);
                        }
                    }
                    zipOutputStream.Finish();
                }
                return zippedMemoryStream.GetBuffer();
            }
        }

        /// <summary>
        /// Uncompresses and deserializes a XamlaElements froma give file stream array
        /// </summary>
        /// <param name="file">The file stream data.</param>
        /// <returns></returns>
        internal List<IXamlaElement> UnCompressProject(byte[] file)
        {
            var list = new List<IXamlaElement>();
            var uni = new UnicodeEncoding();

            using (Stream s = new MemoryStream(file))
            {
                using (ZipInputStream zipInputStream = new ZipInputStream(s))
                {
                    var entry = zipInputStream.GetNextEntry();
                    while (entry != null)
                    {
                        byte[] buffer = new byte[entry.Size];
                        zipInputStream.Read(buffer, 0, buffer.Length);
                        var jsonString = uni.GetString(buffer, 0, buffer.Length);

                        switch (entry.Name.Split(new char[] { '_' })[0])
                        {
                            case "TextBlockView":
                                {
                                    var myType = Container.AppContainer.Resolve<TextBlockView>();
                                    myType.VM = JSONHelper.ConvertJsonStringToObject<TextBlockViewModel>(jsonString);
                                    myType.VM.PostDeserializeActions();
                                    list.Add(myType);
                                } 
                                break;
                            case "EllipseView":
                                {
                                    var myType = Container.AppContainer.Resolve<EllipseView>();
                                    myType.VM = JSONHelper.ConvertJsonStringToObject<EllipseViewModel>(jsonString);
                                    myType.VM.PostDeserializeActions();
                                    list.Add(myType);
                                } 
                                break;
                            case "RectangleView":
                                {
                                    var myType = Container.AppContainer.Resolve<RectangleView>();
                                    myType.VM = JSONHelper.ConvertJsonStringToObject<RectangleViewModel>(jsonString);
                                    myType.VM.PostDeserializeActions();
                                    list.Add(myType);
                                } 
                                break;
                            case "BorderView":
                                {
                                    var myType = Container.AppContainer.Resolve<BorderView>();
                                    myType.VM = JSONHelper.ConvertJsonStringToObject<BorderViewModel>(jsonString);
                                    myType.VM.PostDeserializeActions();
                                    list.Add(myType);
                                }
                                break;
                            case "ImageView":
                                {
                                    var myType = Container.AppContainer.Resolve<ImageView>();

                                    //get the image data from the next file...
                                    var imageEntry = zipInputStream.GetNextEntry();
                                    var imageBuffer = new byte[imageEntry.Size];
                                    zipInputStream.Read(imageBuffer, 0, imageBuffer.Length);                                   
                                    ImageViewModel.ImageData = ConvertToInt32Array(imageBuffer);

                                    myType.VM = JSONHelper.ConvertJsonStringToObject<ImageViewModel>(jsonString);
                                    myType.VM.PostDeserializeActions();
                                    list.Add(myType);

                                }
                                break;
                        }
                        
                        entry = zipInputStream.GetNextEntry();
                    }
                }
            }

            return list;
        }

        /// <summary>
        /// Helper function that converts int32[] to byte[]
        /// </summary>
        /// <param name="pixels"></param>
        /// <returns></returns>
        private static byte[] ConvertToByteArray(int[] pixels)
        {
            var getBytes = new byte[pixels.Length * 4];
            for (var i = 0; i < pixels.Length; i++)
            {
                var convertedInt = BitConverter.GetBytes(pixels[i]);
                convertedInt.CopyTo(getBytes, i * 4);
            }
            return getBytes;
        }

        /// <summary>
        /// Helper function that converts byte[] to int32[]
        /// </summary>
        /// <param name="bytes"></param>
        /// <returns></returns>
        private static int[] ConvertToInt32Array(byte[] bytes)
        {
            var getInts = new int[bytes.Length / 4];
            for (var i = 0; i < getInts.Length; i++)
            {
                getInts[i] = BitConverter.ToInt32(bytes, i * 4);
            }
            return getInts;
        }

        #region IDisposable Members

        public void Dispose()
        {
        }

        #endregion
    }
}
